/**
 * Observer 负责观察数据，将其转换成getter、setter形式
 * Dep 负责收集数据依赖列表，上接Observer/下接Watcher
 * Watcher 实际上的数据依赖[监听者]，负责将数据的变化转发到外界（渲染、回调）
 * 
 * 首先：将data传入Observer转成getter/setter形式；
 * 其次：当Watcher实例读取数据时，会触发getter，将其收集到Dep依赖仓库列表中；
 * 最后：当数据更新时，触发setter，通知Dep仓库中所有的Watcher实例更新；
 */
// Observer
const Observer = function (data) {
  for (let key in data) {
    defineReactive(data, key)
  }
}
const defineReactive = function (obj, key) {
  const dep = new Dep()
  let val = obj[key]
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    // 此处并不会立即执行，而是等Watcher实例读取数据
    get() {
      dep.depend()
      return val
    },
    set (newValue) {
      if (newValue === val) {
        return
      }
      val = newValue
      dep.notify()
    }
  })
}
const observe = function (data) {
  return new Observer(data)
}

// Dep
const Dep = function () {
  const self = this
  this.target = null
  this.subs = []
  // 依赖收集，Watcher实例读取数据时触发getter，推入subs订阅者队列
  this.depend = function () {
    if (Dep.target) {
      Dep.target.addDep(self)
    }
  }
  this.addSub = function (watcher) {
    self.subs.push(watcher)
  }
  this.notify = function () {
    for (let i = 0; i < self.subs.length; i++) {
      self.subs[i].update()
    }
  }
}

// Watcher
const Watcher = function (vm, fn) {
  const self = this
  // Watcher实例读取数据，设置依赖目标为自身，推入subs订阅者队列
  Dep.target = this
  this.addDep = function (dep) {
    dep.addSub(self)
  }
  this.update = function () {
    fn()
  }
  // 这里会首次调用vm._render，从而触发text的get，从而将当前的Wathcer与Dep关联起来
  fn()
  // 推完设置为null
  Dep.target = null
}

// Vue 响应式
export const Vue = function (opt) {
  const self = this
  // 设置data
  if (opt && typeof opt.data === 'function') {
    this._data = opt.data.apply(this)
  }
  // 数据设置为getter、setter
  observe(this._data)

  // 渲染函数-读取数据，trigger getter
  this.render = function () {
    document.getElementById(opt.el).innerHTML = `${self._data.text} ${self._data.msg}`
  }
  // 挂载，（此处挂载可指定DOM对象，渲染机制模块），开始监听，(target, callback)
  this.mount = function () {
    new Watcher(self, self.render)
    return self
  }
}
